Spring Data JPA @Query

您所在的位置:网站首页 jpa query by example Spring Data JPA @Query

Spring Data JPA @Query

#Spring Data JPA @Query| 来源: 网络整理| 查看: 265

If you need to quickly create a JPA-based repository layer, Spring Data JPA is the right choice. You define your repository interface by extending one of the Spring Data JPA Repository interfaces.  At runtime, Spring Data JPA will create your repository implementations with the common CRUD methods. You can then perform CRUD operations without writing a single line of data access code.

But by saying so, Enterprise Applications developed using the Spring Framework often needs to execute complex queries against the database.

In such a scenario, you need to inform Spring Data JPA on what queries you need to execute. You do it using the @Query annotation.

In the post, I will show you how to use @Query annotation to execute custom queries.

The Application

I will use a Spring Boot application with a Book entity.  I will also use Lombok to generate code for the Book entity.

To store entities, I will go with an embedded H2 database.

The Spring Data JPA, Lombok, and H2 dependencies in the pom.xml file is this.

org.springframework.boot spring-boot-starter-data-jpa com.h2database h2 runtime 1.4.199 org.projectlombok lombok 1.18.12 provided

Next, I will add configuration properties in the application.properties file to connect with the embedded H2 database.

The application.properties file is this.

spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.driverClassName=org.h2.Driver

The code of the Book entity is this.

Book.java

package guru.springframework.customquery.domain; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import javax.persistence.*; @Entity(name = "Book") @Builder @Data @NoArgsConstructor @AllArgsConstructor public class Book { @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; @Column(unique=true) private String isbn; private String title; private String author; private boolean status; }

In the preceding code, @Entity(name = "Book") annotation specifies that the class is an entity mapped to a table named Book. The other class-level annotations are Lombok annotations to reduce boilerplate code in this entity class.

Simple @Query

The repository interface extends CrudRepository. Here, I will use the @Query annotation to create a custom query to find all books.

The code of the BookRepository is this.

package guru.springframework.customquery.repository; import guru.springframework.customquery.domain.Book; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; @Repository public interface BookRepository extends CrudRepository { @Query("SELECT b FROM Book b") List findAllBooks(); }

In the preceding code, the findAllBooks() method is annotated with the @Query annotation. This annotation takes a custom query as a string. In this example, the custom query returns all books.

To test this method, I will write a JUnit 5 test.

The BookRepositoryTest class is this.

BookRepositoryTest.java

package guru.springframework.customquery.repository; import guru.springframework.customquery.domain.Book; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import java.util.List; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.hasProperty; @DataJpaTest class BookRepositoryTest { @Autowired private BookRepository bookRepository; private Book book1, book2; private List bookList; @BeforeEach void setUp() { Book book1=Book.builder() .isbn("0-2169-6768-6") .title("I Dare") .author("James Cassy") .status(true) .build(); Book book2=Book.builder() .isbn("0-5395-2414-X") .title("I Dare") .author("Patrick") .status(false) .build(); Book book3=Book.builder() .isbn("0-2139-7448-7") .title("Time Machine") .author("Herbert Neil") .status(false) .build(); Book book4=Book.builder() .isbn("0-4572-6998-3") .title("Time Machine") .author("George Wells") .status(false) .build(); bookRepository.save(book1); bookRepository.save(book2); bookRepository.save(book3); bookRepository.save(book4); } @AfterEach void tearDown() { bookRepository.deleteAll(); } @Test void findAllBooks() { List result = bookRepository.findAllBooks(); assertThat(result, is(notNullValue())); } }

This is an integration test. However, instead of loading the entire application context using @SpringBootTest, I used the @DataJpa annotation. This will load the JPA slice of the application context making the test lightweight and fast.

The setUp() method annotated with @BeforeEach saves Book objects to the database.

The findAllBooks() test method calls the bookRepository.findAllBooks() method.

Finally, the code performs assertions on the returned result using the AssertJ library.

JPQL Select @Query with Index Parameters

One way to pass method parameters to a query is through an index.

Let’s define a custom query using Java Persistence Query Language (JPQL) to find a book for a given title and author.

The code for querying a book with index parameters using JPQL is this.

@Query("SELECT b FROM Book b WHERE b.title = ?1 and b.author = ?2") Book findBookByTitleAndAuthorIndexJpql(String title, String authorName);

In the preceding code, the title method parameter will be assigned to the query parameter with index 1. Similarly, authorName will be assigned to the query parameter with index 2.

It is important to note that the order of the query parameter indexes and the method parameters must be the same.

Therefore, this code will not work.

@Query("SELECT b FROM Book b WHERE b.title = ?2 and b.author = ?1") Book findBookByTitleAndAuthorIndexJpql( String authorName, String title);

The code to test the query is this.

@Test void findBookByTitleAndAuthorIndexJpql() { Book actualRetrievedBook = bookRepository.findBookByTitleAndAuthorIndexJpql("I Dare", "James Cassy"); assertThat(actualRetrievedBook, is(notNullValue())); assertThat(actualRetrievedBook.getIsbn(), is("0-2169-6768-6")); } Native SQL Select @Query with Index Parameters

For a custom Native SQL query, you need to set the nativequery flag to true.

The code to use Native SQL is this.

@Query(value = "SELECT * FROM Book WHERE title = ?1 and author = ?2", nativeQuery = true) Book findBookByTitleAndAuthorIndexNative(String title, String authorName);

In the preceding code, I’m setting the nativeQuery flag to true. At runtime, the title and authorName method parameters will be assigned to the query parameter with index 1 and 2 respectively.

The test code is this.

@Test void findBookByTitleAndAuthorIndexNative() { Book actualRetrievedBook = bookRepository.findBookByTitleAndAuthorIndexNative("I Dare", "James Cassy"); assertThat(actualRetrievedBook, is(notNullValue())); assertThat(actualRetrievedBook.getIsbn(), is("0-2169-6768-6")); } JPQL @Query with Named Parameters

Another approach to pass parameters to a query is with named parameters. In this approach, you pass the method parameter values to the query bind parameters.

In order to do so, use the @Param annotation with the name of the bind parameter in the method definition.

The code for querying a book with named parameters is this.

@Query("SELECT b FROM Book b WHERE b.title = :title and b.author= :author") Book findBookByTitleAndAuthorNamedJpql(@Param("title") String title, @Param("author") String author);

The  @Param annotation in the preceding code binds the names of the query parameters with the method parameters.

The test code is this.

@Test void findBookByTitleAndAuthorNamedJpql() { Book actualRetrievedBook = bookRepository.findBookByTitleAndAuthorNamedJpql("I Dare", "James Cassy"); assertThat(actualRetrievedBook, is(notNullValue())); assertThat(actualRetrievedBook.getIsbn(), is("0-2169-6768-6")); }Native SQL @Query with Named Parameters

To query with named parameters using Native SQL is similar to JPQL.

The code to query is this.

@Query(value = "SELECT * FROM Book WHERE title = :title and author= :author", nativeQuery = true) Book findBookByTitleAndAuthorNamedNative(@Param("title") String title, @Param("author") String author);

The test code is this.

@Test void findBookByTitleAndAuthorNamedNative() { Book actualRetrievedBook = bookRepository.findBookByTitleAndAuthorNamedNative("I Dare", "James Cassy"); assertThat(actualRetrievedBook, is(notNullValue())); assertThat(actualRetrievedBook.getIsbn(), is("0-2169-6768-6")); } Sorting with @Query

To sort query results, in Spring Data JPA provides a special Sort parameter. When you use the Sort parameter, Spring Data JPA will generate the ORDER_BY clause automatically.

The code to perform sorting is this.

@Query("select b from Book b where b.title = ?1") List findBookByTitleAndSort(String title, Sort sort);

In the preceding code, the Sort parameter orders the books retrieved by title according to the sorting properties set on it by the client code.

The test code is this.

@Test void findBookByTitleAndSort() { List actualBookList = bookRepository.findBookByTitleAndSort("Time Machine",Sort.by("author").descending()); assertThat(actualBookList.size(), is(2)); assertThat(actualBookList, containsInAnyOrder( hasProperty("author", is("George Wells")), hasProperty("author", is("Herbert Neil")) )); }

Note: Spring Data JPA does not currently support dynamic sorting for native queries

Spring Expression Language (SpEL) with @Query

Spring Data JPA also supports Spring Expression Language SpEL expressions. An SpEL expression is evaluated against a predefined set of variables. You can replace the actual entity name with entityName expression variable.

The code to demonstrate SpEL expression is this.

@Query("select b from #{#entityName} b where b.title = ?1") List findBookByTitleSPEL(String title);

In the preceding code, Spring Data JPA inserts the entityName of the domain type associated with the given repository, Book in our case.

The test code is this.

@Test void findBookByTitleSPEL() { List actualBookList = bookRepository.findBookByTitleSPEL("I Dare"); assertThat(actualBookList.size(), is(2)); } Pagination with @Query

Spring Data JPA provides a specialPageableparameter to paginate query results.

With Pagination, you get a Page object as a result. A Page object is the subset of the complete result.

The code to perform pagination is this.

@Query(value = "SELECT b FROM Book b ORDER BY title") Page findAllBooksWithPagination(Pageable pageable);

The test code is this.

@Test void findAllBooksWithPagination() { Page allBookWithPagination = bookRepository.findAllBooksWithPagination(PageRequest.of(0, 3)); assertThat(allBookWithPagination.getTotalPages(), is(2)); }

Summary

When you are into Enterprise Application Development with the Spring Framework, you will have to deal with complex queries.

For such queries, you cannot always look for the comfort of Derived queries. For example, if you use more than 2-3 query parameters or need to define multiple joins to other entities, you need a more flexible approach.

In such situations, you can use Spring Data JPA’s @Query annotation to specify a custom JPQL or native SQL query.

With @Query Spring Data JPA provides the required JPA code to execute the statement as a JPQL or native SQL query. Your preferred JPA implementation,  such as, Hibernate or EclipseLink, will then execute the query and map the result.

Another advantage of using the Spring Data JPA @Query annotation is related to code manageability.

With @Query, you are binding the queries to the Java method that executes them. By using this approach over annotating the domain class, you free the domain class from storing any persistence specific information. Also, it is convenient to co-locate the queries in the repository interface.

The source code for this post can be found here on GitHub.

 



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3